﻿/* Luiz Felipe Netto
*
*  Rendering em Tempo Real
*  Trabalho 3: Deferred Shading
*
* O objetivo deste trabalho é renderizar uma cena fazendo uso de framebuffer objects e a
* técnica de iluminação Deferred Shading.
*
* A implementação elaborada tem como objetivo servir de base para este e outros trabalhos
* em OpenGL ao longo do semestre (2014/1), a qual será constantemente aperfeiçoada.
* O projeto completo (vs2013 sem libs) e atualizado pode ser acessado em:
*   https://code.google.com/p/rendering-base/source/browse/
*
* O trabalho é composto dos seguintes itens:
* - Uma cena com 100 esferas e 50 fontes de luz posicionadas acima das bolinhas de forma 
    intercalada (como um tabuleiro de xadrez). A cor de cada luz é inicializada de forma randomica.
    É possível habilitar o movimento das luzes através da tecla M.
* - A classe Deferred Shading foi baseada na técnica descrita no livro OpenGL Super Bible, 
*   6th. por Sellers, et al.
*   Esta classe tem a função de inicializar os buffers e no loop principal passar os  
*   dados para os 'gBuffer', logo após renderizar o quad com a textura iluminada via shader.
* - Shaders gBufferW.{vert,frag} baseados na abordagem do OpenGL SuperBible, utiliza-se apenas 
    2 buffers. 
    O buffer color0 como uvec4 para armazenar a cor e as normais e o buffer color1 para armazenar
    a posição do vértice (no espaço do olho).
* - Shaders defShade.{vert,frag} para tratar a iluminação por pixel, realiza unpack do buffer 
    preenchido na primeira passada e aplica a iluminação.
    Uma váriavel de atenuação foi utilizada para permitir uma melhor visualização das componentes
    especulares.    
*/
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <vector>

#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

#include "manager\callbacks.h"
#include "manager\FramesControl.h"
#include "manager\Mouse.h"
#include "manager\Camera.h"
#include "glsl\Glsl.h"
#include "glsl\Structs.h"
#include "glsl\Grid.h"
#include "glsl\Sphere.h"
#include "glsl\DeferredShading.h"

using namespace glm;

Mouse *mouse;
Camera *camera;
int width = 800;
int height = 600;

// General Init Functions 
GLFWwindow* glfwCfgInit (void);
void glfwCbSetup (GLFWwindow* window);
void openglCfgInit ();
void generalInit ();
void initLight ();

// Draw functions
void draw (); 
void drawLight ();

//Scene 0 Objects
//Glsl *p = NULL;
Glsl *fstPass = NULL;
Glsl *sndPass = NULL;
Grid *grid_0 = NULL;
Sphere *sphere_0 = NULL;

//Deferred Shading globals
int numLights = 50;
vec4 *light;
vec3 *lightColor;
float randCounter = 0;
bool gMove = false;

int main (void)
{
   srand (1000);
   GLFWwindow* window = NULL;
   generalInit (); // Mouse, Camera and other global objects
   window = glfwCfgInit (); //Init GLFW and OGL Context
   openglCfgInit ();
   glfwCbSetup (window);
   glfwSetCursorPos (window, width / 2.0, height / 2.0);

   FramesControl *fps = new FramesControl (60, window);
   fps->setActive (true); //Debug

   fstPass = new Glsl ("src/shader/gBufferW.vert", NULL, "src/shader/gBufferW.frag");   
   fstPass->link ();

   sndPass = new Glsl ("src/shader/defShade.vert", NULL, "src/shader/defShade.frag");
   sndPass->link ();

   fstPass->setUniformBlock (mat4 (1), mat4 (1), camera->getProjection ());
   // Init Objects
   grid_0 = new Grid (10, 10, Vertex::type::P_AND_N);
   grid_0->init ();

   sphere_0 = new Sphere (32, 32, Vertex::type::P_AND_N);
   sphere_0->init ();

   DeferredShading *df = new DeferredShading (width, height);
   df->initGBuffer ();

   //MainLoop   
   while (!glfwWindowShouldClose (window)) {
      if (fps->update (width, height) == true) {

         glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
         glfwGetFramebufferSize (window, &width, &height);

         camera->update ();

         df->renderToGBuffer ();
         fstPass->useProgram (true);
         draw ();
         fstPass->useProgram (false);

         sndPass->useProgram (true);         
         drawLight ();
         df->renderFromGBuffer ();
         sndPass->useProgram (false);

         glfwSwapBuffers (window);
         glfwPollEvents ();
      }
   }

   glfwDestroyWindow (window);
   glfwTerminate ();

   exit (EXIT_SUCCESS);
}

// Draw functions
void draw ()
{
   mat4 gTransform = translate (vec3 (0.f, -0.1, 0.f))*rotate (-90.f, vec3 (1.0f, 0.f, 0.f))*scale (vec3 (20.0f, 20.0f, 20.0f));

   fstPass->setUniform ("modelView", camera->getTransform ()*gTransform);
   fstPass->setUniform ("normalMatrix", inverse (transpose (mat3 (camera->getTransform ()*gTransform))));
   fstPass->setUniform ("objColor", vec4 (0, 1.0, 0, 1.0));
   grid_0->draw ();

   fstPass->setUniform ("objColor", vec4 (1.0, 0, 0, 1.0));
   float d = 2 * 10.f / 10;
   float p0 = -10.f + d / 2;
   for (int i = 0; i < 10; i++) {
      for (int j = 0; j < 10; j++) {
         mat4 transform = translate (vec3 (p0 + i*d, 0.5f, p0 + j*d))*scale (vec3 (0.5f, 0.5f, 0.5f));
         fstPass->setUniform ("modelView", camera->getTransform ()*transform);
         fstPass->setUniform ("normalMatrix", inverse (transpose (mat3 (camera->getTransform ()*transform))));
         sphere_0->draw ();
      }
   }

}

// General Init Functions 
void generalInit ()
{
   camera = new Camera ((float)width, (float)height);
   mouse = new Mouse ();
   initLight ();
}
GLFWwindow*  glfwCfgInit (void)
{
   GLFWwindow* window;

   glfwSetErrorCallback (errorCallback);

   if (glfwInit () == false) {
      std::cerr << "Error initializing glfw" << std::endl;
      exit (EXIT_FAILURE);
   }
   glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 4); // Need to be at least OpenGL 3.3
   glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 4);
   glfwWindowHint (GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

   window = glfwCreateWindow (width, height, "Simple example", NULL, NULL);
   if (!window) {
      glfwTerminate ();
      std::cerr << "Error creating glfw window" << std::endl;
      exit (EXIT_FAILURE);
   }

   glfwMakeContextCurrent (window);
   glfwSetInputMode (window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
   glfwSetWindowPos (window, 20, 50);
   return window;
}
void glfwCbSetup (GLFWwindow* window)
{
   glfwSetCursorPosCallback (window, mouseCursorCallback);
   glfwSetMouseButtonCallback (window, mouseCallback);
   glfwSetKeyCallback (window, keyCallback);
   glfwSetErrorCallback (errorCallback);
   glfwSetWindowSizeCallback (window, windowSizeCallback);
   glfwSetFramebufferSizeCallback (window, framebufferSizeCallback);
}
void openglCfgInit ()
{
   glewExperimental = true;
   GLenum err = glewInit ();
   if (GLEW_OK != err) {
      fprintf (stderr, "Error: %s\n", glewGetErrorString (err));
   }
   fprintf (stdout, "Status: Using GLEW %s\n", glewGetString (GLEW_VERSION));

   const GLubyte *renderer = glGetString (GL_RENDERER);
   const GLubyte *vendor = glGetString (GL_VENDOR);
   const GLubyte *version = glGetString (GL_VERSION);
   const GLubyte *glslVersion =
   glGetString (GL_SHADING_LANGUAGE_VERSION);
   GLint major, minor;
   glGetIntegerv (GL_MAJOR_VERSION, &major);
   glGetIntegerv (GL_MINOR_VERSION, &minor);
   printf ("GL Vendor : %s\n", vendor);
   printf ("GL Renderer : %s\n", renderer);
   printf ("GL Version (string) : %s\n", version);
   printf ("GL Version (integer) : %d.%d\n", major, minor);
   printf ("GLSL Version : %s\n", glslVersion);

   glClearColor (1.0f, 1.0f, 1.0f, 1.0f);
   glEnable (GL_DEPTH_TEST);
   glEnable (GL_CULL_FACE);
   glCullFace (GL_BACK);
   glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);

}
void initLight ()
{
   light = new vec4[numLights];
   lightColor = new vec3[numLights];

   float d = 2 * 10.f / 10;
   float p0 = -10.f + d / 2;
   int k = 0;
   float color = 0;
   for (int i = 0; i < 10; i++) {
      int j;
      if (i % 2 == 0)
         j = 0;
      else
         j = 1;
      for (; j < 10; j+=2) {
         if (k < numLights) {
            color = (float)(rand () % 255);
            lightColor[k].x = color / 255.0f;
            color = (float)(rand () % 255);
            lightColor[k].y = color / 255.0f;
            color = (float)(rand () % 255);
            lightColor[k].z = color / 255.0f;
            light[k++] = vec4 (p0 + i*d, 3.0f, p0 + j*d, 1.0f);
         }         
      }
   }  
   if (k > numLights) {
      printf ("\n\nTried to insert %d more lights\n\n", k - numLights);
   }
   //Debug
   //lightColor[0] = vec3 (1, 1, 1);
}
void drawLight ()
{
   vec3 *lightEyeSpc = new vec3[numLights];
   mat4 tmpTransform;
   float randTr = (float)(rand () % 10);
   for (int i = 0; i < numLights; i++) {
      randCounter += 0.001f;      
      if (i < 25) {
         tmpTransform = translate (vec3 (10.0f*sin (randCounter), 3.0f + 2.0f*sin (randCounter), 10.0f*cos (randCounter)));
         if (i < 12)
            tmpTransform = translate (vec3 (10.0f*sin (randCounter), 3.0f + 2.0f*cos (randCounter), 10.0f*sin (randCounter)));
      }
      else {
         tmpTransform = translate (vec3 (10.0f*cos (randCounter), 3.0f + sin (randCounter) + cos (randCounter), 10.0f*sin (randCounter)));
         if (i < 37) {
            tmpTransform = translate (vec3 (10.0f*cos (randCounter), 3.0f - sin (randCounter) - cos (randCounter), 10.0f*cos (randCounter)));
         }
      }
      //Debug
      //light[0] = vec4 (0, 6, 4, 1);
      vec4 tmp = vec4(0);
      if (gMove == false) {
         tmp = camera->getTransform ()*light[i];
      }
      else {
         tmp = camera->getTransform()*tmpTransform*light[i];
      }
      lightEyeSpc[i] = vec3 (tmp.x, tmp.y, tmp.z);
   }   
   sndPass->setUniform ("lightVec", numLights, lightEyeSpc);
   sndPass->setUniform ("lightColor", numLights, lightColor);
}
// Callback Functions 
static void errorCallback (int error, const char* description)
{
   fputs (description, stderr);
}

//Just a temporary approach
static void keyCallback (GLFWwindow* window, int key, int scancode, int action, int mods)
{
   bool update = true;
   switch (key) {
      case GLFW_KEY_ESCAPE:
         if (action == GLFW_RELEASE) {
            glfwSetWindowShouldClose (window, GL_TRUE);
         }
         break;
      case GLFW_KEY_W:
         if (action == GLFW_PRESS) {
            camera->setMovDir (camera->FWD);
         }
         else if (action == GLFW_RELEASE) {
            camera->unSetMovDir (camera->FWD);
         }
         break;
      case GLFW_KEY_S:
         if (action == GLFW_PRESS) {
            camera->setMovDir (camera->BWD);
         }
         else if (action == GLFW_RELEASE) {
            camera->unSetMovDir (camera->BWD);
         }
         break;
      case GLFW_KEY_A:
         if (action == GLFW_PRESS) {
            camera->setMovDir (camera->STL);
         }
         else if (action == GLFW_RELEASE) {
            camera->unSetMovDir (camera->STL);
         }
         break;
      case GLFW_KEY_D:
         if (action == GLFW_PRESS) {
            camera->setMovDir (camera->STR);
         }
         else if (action == GLFW_RELEASE) {
            camera->unSetMovDir (camera->STR);
         }
         break;
      case GLFW_KEY_SPACE:
         if (action == GLFW_PRESS) {
            camera->setMovDir (camera->UP);
         }
         else if (action == GLFW_RELEASE) {
            camera->unSetMovDir (camera->UP);
         }
         break;
      case GLFW_KEY_X:
         if (action == GLFW_PRESS) {
            camera->setMovDir (camera->DOWN);
         }
         else if (action == GLFW_RELEASE) {
            camera->unSetMovDir (camera->DOWN);
         }
         break;
      case GLFW_KEY_M:
         if (action == GLFW_RELEASE) {
            if (gMove == true)
               gMove = false;
            else
               gMove = true;
         }         
         break;
      case GLFW_KEY_Q:
         //
         break;
      default:
         update = false;
         break;
   }
   if (update == false) {
      if (action == GLFW_RELEASE) {
         printf ("%c Released\n", key);
      }
      else {
         printf ("%c Pressed\n", key);
      }
   }
   else {
      //printf (" %c: Pos( %.2f, %.2f, %.2f )\n", key, camera->getPosition ().x, camera->getPosition ().y, camera->getPosition ().z);
   }
}
static void mouseCallback (GLFWwindow* window, int button, int action, int mods)
{
   mouse->setButton (button, action, mods);
   printf ("(%.2lf, %.2lf) bt:%d act:%d mod:%d\n", mouse->getPos ().x, mouse->getPos ().y, mouse->getButton (), mouse->getAction (), mouse->getMods ());
}
static void mouseCursorCallback (GLFWwindow* window, double px, double py)
{
   //printf ("x: %.2lf y: %.2lf\n", px, py);
   mouse->setPos (px, py);
   camera->setMouse (vec2 (px, py));
}
static void windowSizeCallback (GLFWwindow *window, int width, int height)
{
   std::cout << "Entrou " << width << " " << height << std::endl;
   //glfwSetWindowSize (window, width, height);
   camera->setDimension ((float)width, (float)height);
}
static void framebufferSizeCallback (GLFWwindow* window, int width, int height)
{
   glViewport (0, 0, width, height);
}
